/*
 * Copyright (C) 2011 Reeny
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package de.tobidodo.spritmonclient.data;

import java.sql.SQLException;
import java.util.HashMap;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

/**
 * @author Reeny
 *
 */
public class FuelDataProvider extends ContentProvider {
	private static final String PROVIDER_ID = "de.tobidodo.spritmonclient.fueldataprovider";
	public static final String AUTHORITY = PROVIDER_ID;

	public static final String CONTENT_PATH = "fueldata";
	
	public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+CONTENT_PATH);

	public static final Uri CONTENT_LIST_URI = Uri.parse("content://"+PROVIDER_ID+"/"+CONTENT_PATH);
	public static final Uri CONTENT_ITEM_URI = Uri.parse("content://"+PROVIDER_ID+"/"+CONTENT_PATH+"/");

	/**
	 * The MIME type of {@link #CONTENT_URI} providing a directory of fuel data.
	 */
	public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.tobidodo.fueldata";

	/**
	 * The MIME type of a {@link #CONTENT_URI} sub-directory of a single fuel data.
	 */
	public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.tobidodo.fueldata";



	/** 0-relative position of a item ID segment in the path part of a fuel data item URI. */
	public static final int ITEM_ID_PATH_POSITION = 1;


	private static final String SPRITMON_DB_NAME = "spritmonDb";
	private static final String TBLNAME_FUELDATA = "fueldata";
	private static final int DATABASE_VERSION = 8;

	/** Primary key. Type: integer. */
	public static final String _ID = "_id";
	/** Fuel data attribute. Type: text. */
	public static final String FUEL_DATE = "fuel_date";
	/** Fuel data attribute. Type: text. */
	public static final String TOT_DIST = "total_dist";
	/** Fuel data attribute. Type: text. */
	public static final String LAST_DIST = "last_dist";
	/** Fuel data attribute. Type: text. */
	public static final String FUEL_IN = "fuel_input";
	/** Fuel data attribute. Type: text. */
	public static final String FUEL_COST = "fuel_cost";
	/** Fuel data attribute. Type: text. */
	public static final String CREATION_TIME = "created_time";
	/** Fuel data attribute. Type: text. */
	public static final String UPDATE_TIME = "updated_time";
	/** Fuel data attribute. Type: text. */
	public static final String TRANSMIT_TIME = "transmit_time";
	/** Fuel data attribute. Type: integer. */
	public static final String ERROR = "last_error";

	static final String FUELDATA_CREATE_STMNT =
			"create table " + TBLNAME_FUELDATA + " (" +
					_ID + " integer primary key autoincrement, " +
					FUEL_DATE + " text, " +
					TOT_DIST + " text, " +
					LAST_DIST + " text, " +
					FUEL_IN + " text, " +
					FUEL_COST + " text, " +
					CREATION_TIME + " text, " +
					UPDATE_TIME + " text, " +
					TRANSMIT_TIME + " text, " +
					ERROR + " integer " +
					");";

	/** The default sort order for this table. */
	public static final String DEFAULT_SORT_ORDER = UPDATE_TIME+" desc";

	/** The incoming URI matches the Fuel data list URI pattern. */
	private static final int URI_IS_FUELDATA_LIST = 1;

	/** The incoming URI matches the Fuel data ID URI pattern. */
	private static final int URI_IS_FUELDATA_ITEM = 2;

	/**
	 * A projection map used to select columns from the database
	 */
	private static HashMap<String, String> fueldataProjectionMap;

	/**
	 * A UriMatcher instance.
	 */
	private static final UriMatcher uriMatcher;


	private OpenHelper openHelper;



	/**
	 * A block that instantiates and sets static objects
	 */
	static {

		/* Creates and initializes the URI matcher. */
		uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

		/* Add a pattern that routes URIs terminated with "fueldata" to a URI_IS_FUELDATA_LIST operation. */
		uriMatcher.addURI(PROVIDER_ID, CONTENT_PATH, URI_IS_FUELDATA_LIST);

		/* Add a pattern that routes URIs terminated with "fueldata" plus an integer to a note ID operation. */
		uriMatcher.addURI(PROVIDER_ID, CONTENT_PATH+"/#", URI_IS_FUELDATA_ITEM);


		// Creates a new projection map instance. The map returns a column name
		// given a string. The two are usually equal.
		fueldataProjectionMap = new HashMap<String, String>();

		// Maps the string "_ID" to the column name "_ID"
		fueldataProjectionMap.put(_ID, _ID);
		fueldataProjectionMap.put(FUEL_DATE, FUEL_DATE);
		fueldataProjectionMap.put(TOT_DIST, TOT_DIST);
		fueldataProjectionMap.put(LAST_DIST, LAST_DIST);
		fueldataProjectionMap.put(FUEL_IN, FUEL_IN);
		fueldataProjectionMap.put(FUEL_COST, FUEL_COST);
		fueldataProjectionMap.put(CREATION_TIME, CREATION_TIME);
		fueldataProjectionMap.put(UPDATE_TIME, UPDATE_TIME);
		fueldataProjectionMap.put(TRANSMIT_TIME, TRANSMIT_TIME);
		fueldataProjectionMap.put(ERROR, ERROR);

	}


	/**
	 * This is called when a client calls
	 * {@link android.content.ContentResolver#delete(Uri, String, String[])}.
	 * Deletes records from the database. If the incoming URI matches the fuel data item URI pattern,
	 * this method deletes the one record specified by the ID in the URI. Otherwise, it deletes a
	 * a set of records. The record or records must also match the input selection criteria
	 * specified by where and whereArgs.
	 *
	 * If rows were deleted, then listeners are notified of the change.
	 * @return If a "where" clause is used, the number of rows affected is returned, otherwise
	 * 		0 is returned. To delete all rows and get a row count, use "1" as the where clause.
	 * @throws IllegalArgumentException if the incoming URI pattern is invalid.
	 * @see android.content.ContentProvider#delete(android.net.Uri, java.lang.String, java.lang.String[])
	 */
	@Override
	public int delete(Uri uri, String where, String[] whereArgs) {

		// Opens the database object in "write" mode.
		SQLiteDatabase db = openHelper.getWritableDatabase();
		String finalWhere;

		int count;

		// Does the delete based on the incoming URI pattern.
		switch (uriMatcher.match(uri)) {

		// If the incoming pattern matches the general pattern for fuel data, does a delete
		// based on the incoming "where" columns and arguments.
		case URI_IS_FUELDATA_LIST:
			count = db.delete(
					TBLNAME_FUELDATA,  // The database table name
					where,                     // The incoming where clause column names
					whereArgs                  // The incoming where clause values
					);
			break;

			// If the incoming URI matches a single fuel data ID, does the delete based on the
			// incoming data, but modifies the where clause to restrict it to the
			// particular fuel ID.
		case URI_IS_FUELDATA_ITEM:
			/* Starts a final WHERE clause by restricting it to the desired fuel data ID. */
			finalWhere =
			_ID +                              // The ID column name
			" = " +                                          // test for equality
			uri.getPathSegments().                           // the incoming note ID
			get(ITEM_ID_PATH_POSITION)
			;

			// If there were additional selection criteria, append them to the final
			// WHERE clause
			if (where != null) {
				finalWhere = finalWhere + " AND " + where;
			}

			// Performs the delete.
			count = db.delete(
					TBLNAME_FUELDATA,  // The database table name.
					finalWhere,                // The final WHERE clause
					whereArgs                  // The incoming where clause values.
					);
			break;

			// If the incoming pattern is invalid, throws an exception.
		default:
			throw new IllegalArgumentException("Unknown URI " + uri);
		}

		/*
		 * Gets a handle to the content resolver object for the current context, and notifies it
		 * that the incoming URI changed. The object passes this along to the resolver framework,
		 * and observers that have registered themselves for the provider are notified.
		 */
		getContext().getContentResolver().notifyChange(uri, null);

		// Returns the number of rows deleted.
		return count;
	}

	/**
	 * This is called when a client calls {@link android.content.ContentResolver#getType(Uri)}.
	 * Returns the MIME data type of the URI given as a parameter.
	 *
	 * @param uri The URI whose MIME type is desired.
	 * @return The MIME type of the URI.
	 * @throws IllegalArgumentException if the incoming URI pattern is invalid.
	 * @see android.content.ContentProvider#getType(android.net.Uri)
	 */
	@Override
	public String getType(Uri uri) {

		/** Chooses the MIME type based on the incoming URI pattern. */
		switch (uriMatcher.match(uri)) {

		// if the pattern is for fuel data list, returns the general content type.
		case URI_IS_FUELDATA_LIST:
			return CONTENT_TYPE;

			// if the pattern is for fuel data IDs, returns the fuel data item content type.
		case URI_IS_FUELDATA_ITEM:
			return CONTENT_ITEM_TYPE;

			// if the URI pattern doesn't match any permitted patterns, throws an exception.
		default:
			throw new IllegalArgumentException("Unknown URI " + uri);
		}
	}

	/**
	 * This is called when a client calls
	 * {@link android.content.ContentResolver#insert(Uri, ContentValues)}.
	 * Inserts a new row into the database. This method sets up default values for any
	 * columns that are not included in the incoming map.
	 * If rows were inserted, then listeners are notified of the change.
	 * @return The row ID of the inserted row.
	 * @throws SQLException if the insertion fails.
	 */
	@Override
	public Uri insert(Uri uri, ContentValues initialValues) {

		// Validates the incoming URI. Only the full provider URI is allowed for inserts.
		if (uriMatcher.match(uri) != URI_IS_FUELDATA_LIST) {
			throw new IllegalArgumentException("Unknown URI " + uri);
		}

		// A map to hold the new record's values.
		ContentValues values;

		// If the incoming values map is not null, uses it for the new values.
		if (initialValues != null) {
			values = new ContentValues(initialValues);
		} 
		else {
			// Otherwise, create a new value map
			values = new ContentValues();
		}

		// Gets the current system time in milliseconds
		Long now = Long.valueOf(System.currentTimeMillis());

		// If the values map doesn't contain the creation date, sets the value to the current time.
		if (! values.containsKey(CREATION_TIME)) {
			values.put(CREATION_TIME, now);
		}

		// If the values map doesn't contain the modification date, sets the value to the current
		// time.
		if (! values.containsKey(UPDATE_TIME)) {
			values.put(UPDATE_TIME, now);
		}


		// Opens the database object in "write" mode.
		SQLiteDatabase db = openHelper.getWritableDatabase();

		// Performs the insert and returns the ID of the new note.
		long rowId = db.insert(
				TBLNAME_FUELDATA,        // The table to insert into.
				null,                     // A hack, SQLite sets this column value to null
				// if values is empty.
				values                   // A map of column names, and the values to insert
				// into the columns.
				);

		// If the insert succeeded, the row ID exists.
		if (rowId > 0) {
			// Creates a URI with the fuel data ID pattern and the new row ID appended to it.
			Uri fuelDataItemUri = ContentUris.withAppendedId(CONTENT_ITEM_URI, rowId);

			// Notifies observers registered against this provider that the data changed.
			getContext().getContentResolver().notifyChange(fuelDataItemUri, null);
			return fuelDataItemUri;
		}

		// If the insert didn't succeed, then the rowID is <= 0. Throws an exception.
		throw new IllegalArgumentException("Failed to insert row into " + uri);
	}

	/**
	 * Creates a new helper object. Note that the database itself isn't opened until
	 * something tries to access it, and it's only created if it doesn't already exist.
	 * @see android.content.ContentProvider#onCreate()
	 */
	@Override
	public boolean onCreate() {
		openHelper = new OpenHelper(getContext());


		// Assumes that any failures will be reported by a thrown exception.
		return true;
	}

	/**
	 * This method is called when a client calls
	 * {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)}.
	 * Queries the database and returns a cursor containing the results.
	 *
	 * @return A cursor containing the results of the query. The cursor exists but is empty if
	 * the query returns no results or an exception occurs.
	 * @throws IllegalArgumentException if the incoming URI pattern is invalid.
	 * @see android.content.ContentProvider#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String)
	 */
	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

		// Constructs a new query builder and sets its table name
		SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
		qb.setTables(TBLNAME_FUELDATA);


		// Choose the projection and adjust the "where" clause based on URI pattern-matching.
		switch (uriMatcher.match(uri)) {
		// If the incoming URI is for fuel data items, chooses the Fuel data list projection
		case URI_IS_FUELDATA_LIST:
		{
			qb.setProjectionMap(fueldataProjectionMap);
			break;
		}
		/* If the incoming URI is for a single fuel data identified by its ID, chooses the
		 * fuel data item projection, and appends "_ID = <fueldataID>" to the where clause, so that
		 * it selects that single item
		 */
		case URI_IS_FUELDATA_ITEM:
		{
			qb.setProjectionMap(fueldataProjectionMap);
			qb.appendWhere(
					_ID +    // the name of the ID column
					"=" +
					// the position of the item ID itself in the incoming URI
					uri.getPathSegments().get(ITEM_ID_PATH_POSITION));
			break;
		}
		default:
			// If the URI doesn't match any of the known patterns, throw an exception.
			throw new IllegalArgumentException("Unknown URI " + uri);
		}


		String orderBy;
		// If no sort order is specified, uses the default
		if (TextUtils.isEmpty(sortOrder)) {
			orderBy = DEFAULT_SORT_ORDER;
		} else {
			// otherwise, uses the incoming sort order
			orderBy = sortOrder;
		}

		// Opens the database object in "read" mode, since no writes need to be done.
		SQLiteDatabase readonlyDb = openHelper.getReadableDatabase();

		/*
		 * Performs the query. If no problems occur trying to read the database, then a Cursor
		 * object is returned; otherwise, the cursor variable contains null. If no records were
		 * selected, then the Cursor object is empty, and Cursor.getCount() returns 0.
		 */
		Cursor c = qb.query(
				readonlyDb,    // The database to query
				projection,    // The columns to return from the query
				selection,     // The columns for the where clause
				selectionArgs, // The values for the where clause
				null,          // don't group the rows
				null,          // don't filter by row groups
				orderBy        // The sort order
				);

		// Tells the Cursor what URI to watch, so it knows when its source data changes
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}

	/**
	 * This is called when a client calls
	 * {@link android.content.ContentResolver#update(Uri,ContentValues,String,String[])}
	 * Updates records in the database. The column names specified by the keys in the values map
	 * are updated with new data specified by the values in the map. If the incoming URI matches the
	 * fuel data item URI pattern, then the method updates the one record specified by the ID in the URI;
	 * otherwise, it updates a set of records. The record or records must match the input
	 * selection criteria specified by where and whereArgs.
	 * If rows were updated, then listeners are notified of the change.
	 *
	 * @param uri The URI pattern to match and update.
	 * @param values A map of column names (keys) and new values (values).
	 * @param where An SQL "WHERE" clause that selects records based on their column values. If this
	 * is null, then all records that match the URI pattern are selected.
	 * @param whereArgs An array of selection criteria. If the "where" param contains value
	 * placeholders ("?"), then each placeholder is replaced by the corresponding element in the
	 * array.
	 * @return The number of rows updated.
	 * @throws IllegalArgumentException if the incoming URI pattern is invalid.
	 * @see android.content.ContentProvider#update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[])
	 */
	@Override
	public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {

		// Opens the database object in "write" mode.
		SQLiteDatabase db = openHelper.getWritableDatabase();
		int count;
		String finalWhere;

		// Does the update based on the incoming URI pattern
		switch (uriMatcher.match(uri)) {

		// If the incoming URI matches the general fuel data pattern, does the update based on
		// the incoming data.
		case URI_IS_FUELDATA_LIST:

			// Does the update and returns the number of rows updated.
			count = db.update(
					TBLNAME_FUELDATA, // The database table name.
					values,                   // A map of column names and new values to use.
					where,                    // The where clause column names.
					whereArgs                 // The where clause column values to select on.
					);
			break;

			// If the incoming URI matches a single fuel data ID, does the update based on the incoming
			// data, but modifies the where clause to restrict it to the particular note ID.
		case URI_IS_FUELDATA_ITEM:
			// From the incoming URI, get the fuel data ID
			String itemId = uri.getPathSegments().get(ITEM_ID_PATH_POSITION);

			/*
			 * Starts creating the final WHERE clause by restricting it to the incoming
			 * fuel data ID.
			 */
			finalWhere = _ID + " = " + itemId; // the incoming fuel data ID


			// If there were additional selection criteria, append them to the final WHERE
			// clause
			if (where !=null) {
				finalWhere = finalWhere + " AND " + where;
			}


			// Does the update and returns the number of rows updated.
			count = db.update(
					TBLNAME_FUELDATA, // The database table name.
					values,                   // A map of column names and new values to use.
					finalWhere,               // The final WHERE clause to use
					// placeholders for whereArgs
					whereArgs                 // The where clause column values to select on, or
					// null if the values are in the where argument.
					);
			break;
			// If the incoming pattern is invalid, throws an exception.
		default:
			throw new IllegalArgumentException("Unknown URI " + uri);
		}

		/*Gets a handle to the content resolver object for the current context, and notifies it
		 * that the incoming URI changed. The object passes this along to the resolver framework,
		 * and observers that have registered themselves for the provider are notified.
		 */
		getContext().getContentResolver().notifyChange(uri, null);

		// Returns the number of rows updated.
		return count;
	}




	private static class OpenHelper extends SQLiteOpenHelper {

		OpenHelper(final Context context) {
			super(context, SPRITMON_DB_NAME, null, DATABASE_VERSION);
		}

		@Override
		public void onCreate(final SQLiteDatabase db) {
			Log.d("FuelDatabase", "run sql: "+FUELDATA_CREATE_STMNT);
			db.execSQL(FUELDATA_CREATE_STMNT);	
		}

		@Override
		public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
			Log.w("FuelDatabase", "Upgrading database from version "+oldVersion+" to "+newVersion+", this will drop tables and recreate.");
			db.execSQL("drop table if exists " + TBLNAME_FUELDATA);
			onCreate(db);
		}
	}
}
